package pers.qianyu.module.image.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.bean.copier.CopyOptions;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.log.LogFactory;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import pers.qianyu.module.comment.service.CommentService;
import pers.qianyu.module.core.comm.Pagination;
import pers.qianyu.module.core.domain.image.converter.ImageConverter;
import pers.qianyu.module.core.domain.image.dto.ImageDTO;
import pers.qianyu.module.core.domain.image.entity.*;
import pers.qianyu.module.core.domain.image.enums.ImageStatusEnum;
import pers.qianyu.module.core.domain.image.model.baiduai.BaiduImageCensorResult;
import pers.qianyu.module.core.domain.image.query.FollowedUserImageQuery;
import pers.qianyu.module.core.domain.image.query.ImageQuery;
import pers.qianyu.module.core.domain.image.query.KeywordsQuery;
import pers.qianyu.module.core.domain.image.vo.ImageVO;
import pers.qianyu.module.core.domain.system.vo.SimpleUserVO;
import pers.qianyu.module.core.util.DateTimeUtil;
import pers.qianyu.module.core.util.TextUtil;
import pers.qianyu.module.core.util.TokenizerUtil;
import pers.qianyu.module.image.cache.DailyRankCache;
import pers.qianyu.module.image.dao.*;
import pers.qianyu.module.image.exception.ImageErrorCodeEnum;
import pers.qianyu.module.image.exception.ImageException;
import pers.qianyu.module.image.provider.IImageCensorProvider;
import pers.qianyu.module.image.service.ImageLabelService;
import pers.qianyu.module.image.service.ImageService;
import pers.qianyu.module.image.service.ImageViewService;
import pers.qianyu.module.security.domain.LoginUser;
import pers.qianyu.module.security.holder.CurrentLoginUserHolder;
import pers.qianyu.module.system.service.SysUserFollowService;
import pers.qianyu.module.system.service.SysUserService;

import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * @author mizzle rain
 * @date 2020-11-05 15:38
 */
@Service
public class ImageServiceImpl implements ImageService {
    @Autowired
    private ImageConverter imageConverter;
    @Autowired
    private ImageDao imageDao;
    @Autowired
    private SysUserService sysUserService;
    @Autowired
    private DailyRankCache dailyRankCache;
    @Autowired
    private SysUserFollowService sysUserFollowService;
    @Autowired
    private LabelDao labelDao;
    @Autowired
    private ImageLabelDao imageLabelDao;
    @Autowired
    private ImageLabelService imageLabelService;
    @Autowired
    private CommentService commentService;
    @Autowired
    private IImageCensorProvider iImageCensorProvider;
    @Autowired
    private ImageLikeDao imageLikeDao;
    @Autowired
    private ImageCollectionDao imageCollectionDao;
    @Autowired
    private NoticeDao noticeDao;
    @Autowired
    private ImageViewService imageViewService;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Long insImage(ImageDTO imageDTO) {
        ImagePO imagePO = imageConverter.convertToPO(imageDTO);
        // 从全局取出当前用户信息
        if (Objects.isNull(imagePO.getCreatedBy())) {
            LoginUser loginUser = CurrentLoginUserHolder.getLoginUser();
            imagePO.setCreatedBy(loginUser.getSysUserVO().getId());
            imagePO.setUpdatedBy(imagePO.getCreatedBy());
        }
        // 检测图片内容
        checkImage(imagePO);
        imageDao.insert(imagePO);
        // 处理标签
        String label = imageDTO.getLabel();
        if (StrUtil.isNotBlank(label)) {
            if (StrUtil.contains(label, '，')) {
                label = StrUtil.replace(label, "，", ",");
            }
            String[] labels = label.split(",");
            for (String la : labels) {
                if (StrUtil.isNotBlank(la)) {
                    LabelPO labelPO = new LabelPO();
                    labelPO.setName(la);
                    LabelPO fromDb = labelDao.selectOne(new QueryWrapper<>(labelPO));
                    if (Objects.nonNull(fromDb)) {
                        labelPO = fromDb;
                    } else {
                        labelDao.insert(labelPO);
                    }
                    ImageLabelPO imageLabelPO = new ImageLabelPO();
                    imageLabelPO.setImageId(imagePO.getId());
                    imageLabelPO.setLabelId(labelPO.getId());
                    imageLabelDao.insert(imageLabelPO);
                }
            }
        }
        return imagePO.getId();
    }

    /**
     * 调用百度API检测图片是否有违规内容
     *
     * @param imagePO 图片信息
     */
    private void checkImage(ImagePO imagePO) {
        BaiduImageCensorResult imageCensorResult = iImageCensorProvider.getImageCensorResult(imagePO.getUrl());
        LogFactory.get().info(JSON.toJSONString(imageCensorResult));
        if (imageCensorResult.getErrorCode() == null) {
            // 不等于1L说明有问题
            if (!Objects.equals(imageCensorResult.getConclusionType(), 1L)) {
                String remark = Arrays.stream(imageCensorResult.getData())
                        .map(BaiduImageCensorResult.Datum::getMsg)
                        .collect(Collectors.joining(";"));
                imagePO.setRemark(remark);
                imagePO.setStatus(ImageStatusEnum.PENDING.getCode());
            }
        } else {
            imagePO.setRemark(imageCensorResult.getErrorMsg());
            imagePO.setStatus(ImageStatusEnum.PENDING.getCode());
        }
    }

    /**
     * 需要注意的是，这里的更新不能更新标签
     *
     * @param id       图片id
     * @param imageDTO 图片信息
     */
    @Override
    public void updImage(Long id, ImageDTO imageDTO) {
        if (Objects.isNull(imageDao.selectById(id))) {
            throw new ImageException(ImageErrorCodeEnum.INVALID_IMAGE_ID);
        }
        ImagePO imagePO = new ImagePO();
        BeanUtil.copyProperties(imageDTO, imagePO, CopyOptions.create().ignoreNullValue());
        imagePO.setId(id);
        LoginUser loginUser = CurrentLoginUserHolder.getLoginUser();
        imagePO.setUpdatedBy(loginUser.getSysUserVO().getId());
        imageDao.updateById(imagePO);
        // 更新标签
        String labelStr = imageDTO.getLabel();
        labelStr = StrUtil.replace(labelStr, "，", ",");
        String[] labels = labelStr.split(",");
        ArrayList<ImageLabelPO> imageLabelPOList = new ArrayList<>();
        for (String la : labels) {
            if (StrUtil.isNotBlank(la)) {
                LabelPO labelPO = new LabelPO();
                labelPO.setName(la);
                LabelPO labelPOFromDB = labelDao.selectOne(new QueryWrapper<>(labelPO));
                if (Objects.nonNull(labelPOFromDB)) {
                    ImageLabelPO imageLabelPO = new ImageLabelPO();
                    imageLabelPO.setLabelId(labelPOFromDB.getId());
                    imageLabelPO.setImageId(id);
                    imageLabelPOList.add(imageLabelPO);
                } else {
                    labelPO.setCreatedBy(CurrentLoginUserHolder.getLoginUser().getSysUserVO().getId());
                    labelPO.setUpdatedBy(labelPO.getCreatedBy());
                    labelDao.insert(labelPO);
                    ImageLabelPO imageLabelPO = new ImageLabelPO();
                    imageLabelPO.setLabelId(labelPO.getId());
                    imageLabelPO.setImageId(id);
                    imageLabelPOList.add(imageLabelPO);
                }
            }
        }
        ImageLabelPO il = new ImageLabelPO();
        il.setImageId(id);
        imageLabelDao.delete(new QueryWrapper<>(il));
        imageLabelPOList.forEach(imageLabelDao::insert);
    }

    @Override
    public Pagination<ImageVO> queryImage(ImageQuery imageQuery) {
        Page<ImagePO> page = new Page<>(imageQuery.getPage(), imageQuery.getLimit());
        QueryWrapper<ImagePO> wrapper = getImagePOQueryWrapper(imageQuery);
        wrapper.orderByDesc("create_time");
        Pagination<ImageVO> pagination = queryImagePageInfo(page, wrapper);
        decorateText(imageQuery, pagination);
        return pagination;
    }

    /**
     * 处理结果中的文字，例如将关键词标红
     *
     * @param imageQuery 查询条件
     * @param pagination 分页结果
     */
    private void decorateText(ImageQuery imageQuery, Pagination<ImageVO> pagination) {
        if (Objects.nonNull(imageQuery.getTitle())) {
            for (ImageVO record : pagination.getRecords()) {
                record.setTitle(TextUtil.replaceStrWithRedText(
                        record.getTitle(),
                        String.format("(%s)", imageQuery.getTitle())
                ));
            }
        }
        if (Objects.nonNull(imageQuery.getDescription())) {
            for (ImageVO record : pagination.getRecords()) {
                record.setDescription(TextUtil.replaceStrWithRedText(
                        record.getDescription(),
                        String.format("(%s)", imageQuery.getDescription())
                ));
            }
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public ImageVO queryById(Long id) {
        dailyRankCache.incr(id);
        //addViews(id, 1L);
        ImagePO imagePO = imageDao.selectById(id);
        if (Objects.isNull(imagePO)) {
            throw new ImageException(ImageErrorCodeEnum.INVALID_IMAGE_ID);
        }
        ImageVO imageVO = imageConverter.convertToVO(imagePO, getLabelsByImageId(imagePO.getId()));
        SimpleUserVO simpleUserVO = sysUserService.querySimpleUserInfoById(imageVO.getCreatedBy());
        imageVO.setCreator(simpleUserVO);
        // 收集浏览信息
        imageViewService.addViewCount(id, simpleUserVO.getId());
        return imageVO;
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void delImage(Long id) {
        imageDao.deleteById(id);
        commentService.deleteByTopicId(id);
        ImageLikePO imageLikePO = new ImageLikePO();
        imageLikePO.setImageId(id);
        imageLikeDao.delete(new QueryWrapper<>(imageLikePO));
        ImageCollectionPO imageCollectionPO = new ImageCollectionPO();
        imageCollectionPO.setImageId(id);
        imageCollectionDao.delete(new QueryWrapper<>(imageCollectionPO));
        NoticePO noticePO = new NoticePO();
        noticePO.setTargetId(id);
        noticeDao.delete(new QueryWrapper<>(noticePO));
    }

    @Override
    public void addComments(Long id, int count) {
        ImagePO imagePO = imageDao.selectById(id);
        if (Objects.isNull(imagePO)) {
            throw new ImageException(ImageErrorCodeEnum.INVALID_IMAGE_ID);
        }
        imagePO.setComments(imagePO.getComments() + count);
        imageDao.updateById(imagePO);
    }

    @Override
    public void addViews(Long id, long count) {
        ImagePO imagePO = imageDao.selectById(id);
        if (Objects.isNull(imagePO)) {
            throw new ImageException(ImageErrorCodeEnum.INVALID_IMAGE_ID);
        }
        imagePO.setViews(imagePO.getViews() + count);
        imageDao.updateById(imagePO);
    }

    @Override
    public List<ImageVO> queryDailyRank(int offset, int limit) {
        List<ImagePO> imagePOList = dailyRankCache.range(limit, offset);
        if (imagePOList.isEmpty()) {
            return new ArrayList<>();
        }
        // 从数据库中查询
        QueryWrapper<ImagePO> wrapper = new QueryWrapper<>();
        Set<Long> imageIds = imagePOList.stream().map(ImagePO::getId).collect(Collectors.toSet());
        wrapper.in("id", imageIds);
        wrapper.in("status", ImageStatusEnum.NORMAL.getCode());
        List<ImagePO> imagePOS = imageDao.selectList(wrapper);
        // 处理结果
        ArrayList<ImageVO> imageVOS = new ArrayList<>();
        for (ImagePO imagePO : imagePOS) {
            Long imageId = imagePO.getId();
            ImagePO fromRedis = CollUtil.findOne(imagePOList, i -> i.getId().equals(imageId));
            String[] labels = getLabelsByImageId(imagePO.getId());
            ImageVO imageVO = imageConverter.convertToVO(imagePO, labels);
            SimpleUserVO simpleUserVO = sysUserService.querySimpleUserInfoById(imageVO.getCreatedBy());
            imageVO.setCreator(simpleUserVO);
            imagePO.setViews(fromRedis.getViews());
            imageVOS.add(imageVO);
        }
        CollUtil.sort(imageVOS, Comparator.comparing(ImageVO::getViews).reversed());
        return imageVOS;
    }

    @Override
    public Pagination<ImageVO> getFollowedUserImages(FollowedUserImageQuery followedUserImageQuery) {
        Page<ImagePO> page = new Page<>(followedUserImageQuery.getPage(), followedUserImageQuery.getLimit());
        QueryWrapper<ImagePO> wrapper = new QueryWrapper<>();
        Set<Long> myFollowIds = sysUserFollowService.getMyFollow(followedUserImageQuery.getUserId());
        if (myFollowIds.isEmpty()) {
            return Pagination.emptyPage(followedUserImageQuery.getPage(), followedUserImageQuery.getLimit());
        }
        wrapper.in("created_by", myFollowIds);
        wrapper.in("status", 0L);
        wrapper.orderByDesc("create_time");
        return queryImagePageInfo(page, wrapper);
    }

    @Override
    public Pagination<ImageVO> queryImageByKeywords(KeywordsQuery keywordsQuery) {
        if (StrUtil.isBlank(keywordsQuery.getKeywords())) {
            return Pagination.emptyPage(keywordsQuery.getPage(), keywordsQuery.getLimit());
        }
        Set<String> keywords = TokenizerUtil.splitKeyWords(keywordsQuery.getKeywords());
        String regex = String.format(".*(%s).*", String.join("|", keywords));
        Integer limit = keywordsQuery.getLimit();
        Integer page = keywordsQuery.getPage();
        List<ImagePO> imagePOS = imageDao.selectImageByRegexOrderByCreateTime(regex, (page - 1) * limit, limit);
        long total = imageDao.selectCountByRegex(regex);
        final String regex2 = String.format("(%s)", String.join("|", keywords));
        List<ImageVO> imageVOS = Optional.ofNullable(imagePOS)
                .map(List::stream)
                .orElseGet(Stream::empty)
                .map(r -> {
                    String[] labels = getLabelsByImageId(r.getId());
                    return imageConverter.convertToVO(r, labels);
                })
                .peek(i -> i.setTitle(TextUtil.replaceStrWithRedText(i.getTitle(), regex2)))
                .collect(Collectors.toList());

        Pagination<ImageVO> pagination = new Pagination<>();
        pagination.setRecords(imageVOS);
        pagination.setCurrent(keywordsQuery.getPage());
        pagination.setSize(keywordsQuery.getLimit());
        pagination.setTotal(total);
        for (ImageVO imageVO : pagination.getRecords()) {
            SimpleUserVO simpleUserVO = sysUserService.querySimpleUserInfoById(imageVO.getCreatedBy());
            imageVO.setCreator(simpleUserVO);
        }
        return pagination;
    }

    @Override
    public Pagination<ImageVO> queryImagePageInfo(Page<ImagePO> page, QueryWrapper<ImagePO> wrapper) {
        Page<ImagePO> imagePOPage = imageDao.selectPage(page, wrapper);
        Pagination<ImageVO> pagination = new Pagination<>(
                Optional.ofNullable(imagePOPage.getRecords())
                        .map(List::stream)
                        .orElseGet(Stream::empty)
                        .map(r -> {
                            String[] labels = getLabelsByImageId(r.getId());
                            return imageConverter.convertToVO(r, labels);
                        })
                        .collect(Collectors.toList()),
                imagePOPage.getTotal(),
                imagePOPage.getSize(),
                imagePOPage.getCurrent()
        );
        for (ImageVO imageVO : pagination.getRecords()) {
            SimpleUserVO simpleUserVO = sysUserService.querySimpleUserInfoById(imageVO.getCreatedBy());
            imageVO.setCreator(simpleUserVO);
        }
        return pagination;
    }

    /**
     * 根据图片id获取图片的所有标签
     *
     * @param imageId 图片id
     * @return 标签数组
     */
    private String[] getLabelsByImageId(Long imageId) {
        try {
            Set<String> set = imageLabelService.getLabelsByImageId(imageId);
            return ArrayUtil.toArray(set, String.class);
        } catch (Exception e) {
            e.printStackTrace();
            return new String[0];
        }
    }

    /**
     * 根据 ImageQuery 构造查询条件 QueryWrapper
     *
     * @param imageQuery 查询条件信息
     * @return 用于查询的 QueryWrapper 对象
     */
    private QueryWrapper<ImagePO> getImagePOQueryWrapper(ImageQuery imageQuery) {
        ImagePO imagePO = new ImagePO();
        imagePO.setCreatedBy(imageQuery.getCreatedBy());
        imagePO.setUpdatedBy(imageQuery.getUpdatedBy());
        if (imageQuery.getStatus() != null) {
            imagePO.setStatus(imageQuery.getStatus());
        }
        QueryWrapper<ImagePO> wrapper = new QueryWrapper<>(imagePO);
        // 处理创建时间
        String createTimeEnd = imageQuery.getCreateTimeEnd();
        String createTimeStart = imageQuery.getCreateTimeStart();
        if (Objects.nonNull(createTimeStart)) {
            if (Objects.nonNull(createTimeEnd)) {
                wrapper.between("create_time", DateTimeUtil.toMillis(createTimeStart),
                        DateTimeUtil.toMillis(createTimeEnd));
            } else {
                wrapper.ge("create_time", DateTimeUtil.toMillis(createTimeStart));
            }
        }
        // 处理更新时间
        String updateTimeStart = imageQuery.getUpdateTimeStart();
        String updateTimeEnd = imageQuery.getUpdateTimeEnd();
        if (Objects.nonNull(updateTimeStart)) {
            if (Objects.nonNull(createTimeEnd)) {
                wrapper.between("update_time", DateTimeUtil.toMillis(updateTimeStart),
                        DateTimeUtil.toMillis(updateTimeEnd));
            } else {
                wrapper.ge("update_time", DateTimeUtil.toMillis(updateTimeStart));
            }
        }
        if (Objects.nonNull(imageQuery.getTitle())) {
            wrapper.like("title", imageQuery.getTitle());
        }
        if (Objects.nonNull(imageQuery.getDescription())) {
            wrapper.like("description", imageQuery.getDescription());
        }
        String labelName = imageQuery.getLabel();
        if (Objects.nonNull(labelName)) {
            List<Long> imageIds = labelDao.selectImageIdsByLabelName(StrUtil.format("%{}%", labelName));
            wrapper.in("id", imageIds);
        }
        return wrapper;
    }
}
